package com.woniuxy.globalfilter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.woniuxy.entity.User;
import com.woniuxy.entity.utils.ResponseEntity;
import com.woniuxy.enums.TokenEnum;
import com.woniuxy.jwt.JWTUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author: 马宇航
 * @Do: 自定义全局过滤器
 * @DateTime: 22/06/28/0028 上午 10:49
 * @Component: 成都蜗牛学苑
 **/
@Component
public class GlobalServerFilter implements GlobalFilter, Ordered {
    //刷新token快要过期的时间阈值 还剩余时间不足30分钟的时候，需要重新刷新token
    public static final Long EXPIRATIONTHRESHOLD = 1800L;
    @Autowired
    RedisTemplate redisTemplate;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String refreshToken = null;
        //获取请求头中refreshToken信息
        if (request.getHeaders().get("RefreshToken")!=null){
            refreshToken = request.getHeaders().get("RefreshToken").get(0);
        }
        System.out.println("refreshToken = " + refreshToken);
        //1.启动auth服务了，如果你是登录请求/auth/login?account=1&；则直接放行
        if (request.getPath().value().equals("/auth/login")) {
            return chain.filter(exchange);
        }
        //2.如果不是登录请求，则需要判断是否有refreshToken 如果请求头没有refreshToken则返回一个500消息
        else if(refreshToken == null || refreshToken.isEmpty()){
            byte[] bytes = returnJsonDataResponse();
            DataBuffer wrap = response.bufferFactory().wrap(bytes);
            response.getHeaders().add("Content-Type","application/json;charset=utf-8");
            //通过响应体写出我们的数据
            return response.writeWith(Mono.just(wrap));
        }
        //不等登录请求，refreshToken也有了，说明用户已经登录过了
        else {
            //校验refreshToken是否有效，是否过期，是否快要过期
            if (redisTemplate.hasKey(refreshToken)) {//去redis中判断有没有这个刷新token
                //redisTemplate中可以获取是否快要过期
                refreshTokenMethod(refreshToken);
                //没过期后，token过期时间比刷新token的时间要短。
                //取出redis中的token，校验是否过期，如果过期，重新生成
               String newToken = checkIfTheTokenHasExpired(refreshToken);
               if (newToken.equals("")){
                   //如果获取到的token是个空的，则直接返回401认证失败，让它重新登录
                   byte[] bytes = returnJsonDataResponse();
                   DataBuffer wrap = response.bufferFactory().wrap(bytes);
                   response.getHeaders().add("Content-Type","application/json;charset=utf-8");
                   //通过响应体写出我们的数据
                   return response.writeWith(Mono.just(wrap));
               }
               //如果新token生成出来，返回给前端，通过响应头返回
                response.getHeaders().add("token",newToken);
            }
        }
        return chain.filter(exchange);//设置过滤器链
    }
    /**
     * 响应状态转换
     * ChangeLog : 1. 创建 (22/06/28/0028 下午 3:47 [马宇航]);
     *
     * @return byte[]
    */
    public byte[] returnJsonDataResponse(){
        ResponseEntity authfailed = ResponseEntity.AUTHFAILED;
        byte[] bytes = null;
        try {
            //手动json转换，把我们的响应体转换成字符数组
            bytes = new ObjectMapper().writeValueAsBytes(authfailed);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return bytes;
    }
    /**
     * 校验token是否过期，如果过期，重新生成新的token
     * ChangeLog : 1. 创建 (22/06/28/0028 下午 3:30 [马宇航]);
     * @param refreshToken
     * @return String 新的token
    */
    private String checkIfTheTokenHasExpired(String refreshToken) {
        //因为我们存入的时候，使用的是Hash的结构：key：Map，refreshToken就是key，"token"是Map的key
        Map entries = redisTemplate.opsForHash().entries(refreshToken);
        String oldToken = (String) entries.get("token");
        User user = (User) entries.get("user");
        //校验oldToken有没有过期
        TokenEnum verify = JWTUtils.verify(oldToken);
        if (verify.equals(TokenEnum.TOKEN_SUCCESS)){
            //token还有效
            return oldToken;
        }else if (verify.equals(TokenEnum.TOKEN_EXPIRE)){
            //过期
            String newToken = JWTUtils.generateToken(user.getUsername());
            //先去redis中修改值,直接替换其中的一个值
            redisTemplate.opsForHash().put(refreshToken,"token",newToken);
            return newToken;
        }else {
            //token有问题，返回一个空串，外面方法判断
            return "";
        }
    }

    /**
     * 判断刷新token 还有多久过期，如果快要过期了，直接续租
     * ChangeLog : 1. 创建 (22/06/28/0028 下午 3:18 [马宇航]);
     *
     * @return void
    */
    private void refreshTokenMethod(String refreshToken) {
        Long expireTime = redisTemplate.getExpire(refreshToken);//过期时间 单位秒
        System.out.println("过期时间"+expireTime);
        if (expireTime < EXPIRATIONTHRESHOLD){
            //重新设置时间为1个小时
            redisTemplate.expire(refreshToken,1, TimeUnit.HOURS);
        }
    }

    /**
     * 优先级顺序配置
     * ChangeLog : 1. 创建 (22/06/28/0028 上午 10:51 [马宇航]);
     *
     * @return int
    */
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
